/*****************************************************
** Author:
** Date: 2019-10-31
*
*****************************************************/

#ifdef OS_LINUX
#include <pthread.h>
#include <sys/time.h>
#include <time.h>
#include <stdio.h>
#include <unistd.h>
#elif defined(OS_DNA)
#include "dna_os.h"
#endif

#include "timer.h"
#include "core.h"

#define LIATMR_POLLING_PERIOD   10 // ms
#define LUA_TIMER_FUNCNAME_LEN  32
#define LUA_TIMER_POOL_SIZE     8

#ifdef OS_LINUX
#define LUA_TIMER_LOCK(p)       pthread_mutex_lock(&p->mutex);
#define LUA_TIMER_UNLOCK(p)     pthread_mutex_unlock(&p->mutex);
#define LUA_TIMER_SLEEP(ms)     usleep(ms * 1000);
#elif defined(OS_DNA)
#define LUA_TIMER_LOCK(p)       dna_mutex_get(p->mutex, DNA_WAIT_FOREVER);
#define LUA_TIMER_UNLOCK(p)     dna_mutex_put(p->mutex);
#define LUA_TIMER_SLEEP(ms)     dna_task_sleep(ms);
#endif

struct lua_timer_ctx
{
    int syscomid_timeout;
    lua_State *l;

#ifdef OS_LINUX
    int task_alive;
    pthread_t tmr_hdl;
    pthread_mutex_t mutex;
#elif defined(OS_DNA)
    dna_timer_handle_t tmr_hdl;
    dna_mutex_handle_t mutex;
#endif
};

struct lua_timer
{
    struct lua_timer *next;
    unsigned int period;
    unsigned int tick;
    char func_cb[LUA_TIMER_FUNCNAME_LEN];
};

static struct lua_timer_ctx timer_ctx;
static struct lua_timer timer_pool[LUA_TIMER_POOL_SIZE];
static struct lua_timer *ptimer_free;
static struct lua_timer *ptimer_using;

/**
 * @brief
 * @param  None
 * @retval None
 */
static void dump_tmr_list(struct lua_timer *tmr, char *str)
{
    struct lua_timer *p;
    int i = 0;

    lua_log_inf("dump luatmr %s", str);
    for (p = tmr; p != NULL; p = p->next) {
        lua_log_inf("[%d] period %d tick %d func_cb %s", i++, p->period, p->tick, p->func_cb);
    }
}

static int timer_new(lua_State* l)
{
    struct lua_timer_ctx *ctx = &timer_ctx;
    struct lua_timer *p;

    if (   lua_type(ctx->l, -1) != LUA_TNUMBER
        || lua_type(ctx->l, -2) != LUA_TSTRING) {
        lua_log_err("timer.new param err");
        return 0;
    }

    int period = luaL_checkinteger(ctx->l, -1);
    char *luastr = (char *)luaL_checkstring(ctx->l, -2);
    lua_log_dbg("try to add tmr %s period %d", luastr, period);

    // check validity
    period = TenthFloor(period, 10);
    if (period < 10) {
        lua_log_err("timer.new period err");
        return 0;
    }

    if (strlen(luastr) >= LUA_TIMER_FUNCNAME_LEN) {
        lua_log_err("timer.new namelen err");
        return 0;
    }

    // insert timer
    if (ptimer_free == NULL) {
        lua_log_wrn("lua timer full");
        return 0;
    }

    LUA_TIMER_LOCK(ctx);

    p = ptimer_free;
    ptimer_free = p->next;
    p->next = ptimer_using;
    ptimer_using = p;

    p->period = p->tick = period;
    strcpy(p->func_cb, luastr);

    LUA_TIMER_UNLOCK(ctx);

    lua_log_dbg("add tmr %s period %d", p->func_cb, period);
    // dump_tmr_list(ptimer_free, "free");
    dump_tmr_list(ptimer_using, "using");
    return 0;
}

static int timer_del(lua_State* l)
{
    struct lua_timer_ctx *ctx = &timer_ctx;
    struct lua_timer *p, *q;

    if (lua_type(ctx->l, -1) != LUA_TSTRING) {
        lua_log_err("timer.new param err");
        return 0;
    }

    char *luastr = (char *)luaL_checkstring(ctx->l, -1);

    if (strlen(luastr) >= LUA_TIMER_FUNCNAME_LEN) {
        lua_log_err("timer.new namelen err");
        return 0;
    }

    lua_log_dbg("try to delete tmr %s", luastr);

    // del timer
    if (ptimer_using == NULL) {
        lua_log_wrn("lua timer empty");
        return 0;
    }

    for (q = p = ptimer_using; p != NULL; p = p->next) {
        if (strcmp(luastr, p->func_cb) != 0) {
            if (p != ptimer_using)
                q = q->next;
            continue ;
        }

        lua_log_dbg("delete tmr %s", p->func_cb);

        LUA_TIMER_LOCK(ctx);

        if (p == ptimer_using)
            ptimer_using = p->next;
        else
            q->next = p->next;

        p->next = ptimer_free;
        ptimer_free = p;

        p->period = p->tick = 0;
        memset(p->func_cb, 0x0, sizeof(p->func_cb));

        LUA_TIMER_UNLOCK(ctx);
        break;
    }

    // dump_tmr_list(ptimer_free, "free");
    dump_tmr_list(ptimer_using, "using");
    return 0;
}

#ifdef OS_LINUX
static void *timer_polling(void *arg)
{
    struct lua_timer_ctx *ctx = &timer_ctx;
    struct lua_timer *p;

    while (ctx->task_alive) {
        usleep(LIATMR_POLLING_PERIOD * 1000);

        for (p = ptimer_using; p != NULL; p = p->next) {

            p->tick -= LIATMR_POLLING_PERIOD;
            if (p->tick)
                continue;

            p->tick = p->period;
            lua_log_dbg("trigger timer %s period %d", p->func_cb, p->period);
            lua_arch_syscom_send(ctx->syscomid_timeout, p, sizeof(struct lua_timer));
        }
    }
    return NULL;
}
#elif defined(OS_DNA)
static void timer_polling(void *arg)
{
    struct lua_timer_ctx *ctx = &timer_ctx;
    struct lua_timer *p;

    for (p = ptimer_using; p != NULL; p = p->next) {

        p->tick -= LIATMR_POLLING_PERIOD;
        if (p->tick)
            continue;

        p->tick = p->period;
        lua_log_dbg("trigger timer %s period %d", p->func_cb, p->period);
        lua_arch_syscom_send(ctx->syscomid_timeout, p, sizeof(struct lua_timer));
    }
}
#endif

static void timeout_handler(unsigned char *pbuf, unsigned int len)
{
    struct lua_timer_ctx *ctx = &timer_ctx;
    struct lua_timer *p = (struct lua_timer *)pbuf;
    int type, err;

    if (len != sizeof(struct lua_timer)) {
        lua_log_wrn("luatmr trigger err");
        return ;
    }

    lua_arch_lock();

    // check function
    if ((type = lua_getglobal(ctx->l, p->func_cb)) != LUA_TFUNCTION) {
        lua_pop(ctx->l, 1);
        lua_arch_unlock();
        lua_log_err("lua_getglobal '%s' type %s err", p->func_cb, lua_typename(ctx->l, type));
        return ;
    }

    lua_log_dbg("'%s' start", p->func_cb);

    print_lua_stack(ctx->l, "timer polling pcall before");
    if ((err = lua_pcall(ctx->l, 0, 0, 0)) != LUA_OK) {
        lua_log_err("lua_pcall failed %d %s", err, lua_tostring(ctx->l, -1));
    }

    print_lua_stack(ctx->l, "timer polling complete");
    lua_arch_unlock();

    lua_log_dbg("'%s' complete", p->func_cb);
}

int luaopen_timer(lua_State *l)
{
    struct lua_timer_ctx *ctx = &timer_ctx;
    int i, ret, type;

    memset(ctx, 0x0, sizeof(struct lua_timer_ctx));
    memset(timer_pool, 0x0, sizeof(timer_pool));
    ptimer_free = timer_pool;
    ptimer_using = NULL;

    for (i = 0; i < (LUA_TIMER_POOL_SIZE-1); ++i)
        timer_pool[i].next = &timer_pool[i+1];

    ctx->l = l;

#ifdef OS_LINUX
    pthread_mutex_init(&ctx->mutex, NULL);

    ctx->task_alive = !0;
    if ((ret = pthread_create(&ctx->tmr_hdl, NULL, timer_polling, ctx)) != 0) {
        lua_log_err("create lua_timer failed %d", ret);
        return -LUA_ARCH_ERR_SYSTMR;
    }
#elif defined(OS_DNA)
    if ((ctx->mutex = dna_mutex_create("luatmr")) == NULL) {
        lua_log_err("create luatmr mutex failed");
        return -LUA_ARCH_ERR_SYSTMR;
    }

    /* timer initialize */
    if ((ctx->tmr_hdl = dna_timer_create("luatmr", LIATMR_POLLING_PERIOD, timer_polling, NULL)) == NULL) {
        lua_log_err("create luatmr timer failed");
        return -LUA_ARCH_ERR_SYSTMR;
    }
#endif

    const char *lib_name = "timer";
    luaL_Reg timer[] = {
        {"new",    timer_new},
        {"delete", timer_del},
        {NULL, NULL}
    };
    luaL_openlib(l, lib_name, timer, 0);
    print_lua_stack(ctx->l, "open lua 'timer'");
    lua_log_inf("open lua 'timer' success");
    return LUA_ARCH_OK;
}

int luaclose_timer(lua_State *l)
{
    struct lua_timer_ctx *ctx = &timer_ctx;

#ifdef OS_LINUX
    ctx->task_alive = 0;
    pthread_join(ctx->tmr_hdl, NULL);
    pthread_mutex_destroy(&ctx->mutex);
#elif defined(OS_DNA)
    dna_timer_delete(ctx->tmr_hdl);
    dna_mutex_delete(ctx->mutex);
#endif

    // dump_tmr_list(ptimer_free, "free");
    dump_tmr_list(ptimer_using, "using");
    lua_log_inf("close lua 'timer'");
    return LUA_ARCH_OK;
}

int lua_arch_timer_init(void)
{
    struct lua_timer_ctx *ctx = &timer_ctx;

    ctx->syscomid_timeout = lua_arch_register_syscom(timeout_handler);
    if (ctx->syscomid_timeout < 0) {
        lua_log_err("create luatmr syscom failed");
        return -LUA_ARCH_ERR_SYSTMR;
    }
    lua_log_inf("regiseter timer syscomid_timeout %d success", ctx->syscomid_timeout);

    lua_log_inf("timer init done");
    return LUA_ARCH_OK;
}

/* End of file */
